﻿using System;
using System.Runtime.InteropServices;
using System.Threading;

/*
 * User: RSny
 * Date: 5/10/2012
 * Time: 10:25 AM
 */

namespace Aspects.Custom.Audio
{
    public abstract class WaveBuffer : IDisposable
    {
        public WaveBuffer(IntPtr waveHandle, int size, bool waveInOut)
        {
            WavePointer = waveHandle;
            buffer = new byte[size];
            WaveInOut = waveInOut;
            
            Header = new WaveHeader();
            HeaderHandle = GCHandle.Alloc(Header, GCHandleType.Pinned); //hHeader
            BufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
            ThisHandle = GCHandle.Alloc(this);
            
            Header.dwUser = (IntPtr)ThisHandle; //userData
            Header.lpData = BufferHandle.AddrOfPinnedObject(); //databuffer
            Header.dwBufferLength = size; //bufferLength
            Header.dwLoops = 1; //loops

            if (WaveInOut)
            {
                WaveHelper.Try(WaveNative.waveInPrepareHeader(WavePointer, ref Header, Marshal.SizeOf(Header)));
                WaveHelper.Try(WaveNative.waveInAddBuffer(WavePointer, ref Header, Marshal.SizeOf(Header)));
            }
            else
            {
                WaveHelper.Try(WaveNative.waveOutPrepareHeader(WavePointer, ref Header, Marshal.SizeOf(Header)));
            }
        }
        ~WaveBuffer(){Dispose();}

        public void Dispose()
        {
            if (Header.lpData != IntPtr.Zero)
            {
                UnprepareHeader();
                HeaderHandle.Free();
                Header.lpData = IntPtr.Zero;
            }
            SoundEvent.Close();
            if (BufferHandle.IsAllocated)
                BufferHandle.Free();
            GC.SuppressFinalize(this);
        }

        public void WaitFor()
        {
            if (Busy)
                Busy = SoundEvent.WaitOne();
            else
                Thread.Sleep(0);
        }
        
        public void OnCompleted()
        {
            SoundEvent.Set();
            Busy = false;
        }

        public static void WaveProc(IntPtr hdrvr, WaveMessage uMsg, int dwUser, ref WaveHeader wavhdr, IntPtr dwParam2)
        {
            GCHandle handle = (GCHandle)wavhdr.dwUser;
            if (uMsg == WaveMessage.WIM_DATA)
            {
                WaveInBuffer buf = (WaveInBuffer)handle.Target;
                buf.OnCompleted();
            }
            if (uMsg == WaveMessage.WOM_DONE)
            {
                WaveOutBuffer buf = (WaveOutBuffer)handle.Target;
                buf.OnCompleted();
            }
        }

        public void UnprepareHeader()
        {
            if (WaveInOut)
                WaveNative.waveInUnprepareHeader(WavePointer, ref Header, Marshal.SizeOf(Header));
            else
                WaveNative.waveOutUnprepareHeader(WavePointer, ref Header, Marshal.SizeOf(Header));
        }

        public WaveHeader Header;
        public AutoResetEvent SoundEvent = new AutoResetEvent(false);
        public bool Busy;
        public byte[] buffer;
        private GCHandle HeaderHandle;
        private GCHandle BufferHandle;
        private GCHandle ThisHandle;
        
        public IntPtr WavePointer;
        public WaveBuffer NextBuffer;

        public int Size {get { return Header.dwBufferLength; }}
        public IntPtr Data{get { return Header.lpData; }}
        public int BytesRecorded {get { return Header.dwBytesRecorded;}}
        /// <summary>
        /// True if it is WaveIn False if it is WaveOut
        /// </summary>
        private bool WaveInOut;
    }
}